Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid loading all tasks to validate a Run#task_name #866

Merged
merged 1 commit into from
Aug 23, 2023

Conversation

davidstosik
Copy link
Contributor

@davidstosik davidstosik commented Aug 18, 2023

Currently, creating a Run record validates that its task_name belongs to the list returned by Task.available_tasks.

Generating this list requires the gem to browse through the task namespace(s) to find and load all tasks in the application (see Task#load_constants).

Although in most situations this should not be a problem, the Shopify monolith defines a relatively large amount of maintenance tasks, which take a considerable amount of time to load.

Loading all tasks is only absolutely necessary when the application wants to display a list of all tasks. In any other scenario, for example testing a single task, it should not be necessary to load all tasks.

This commit makes that possible by making use of Task.named to confirm that the task name is valid.
Because available_tasks relies on .descendants, and Task.named uses < Task to test inheritance, I don't think this will have any unintended effect on the gem's behaviour.

Currently, creating a `Run` record validates that its `task_name`
belongs to the list returned by `Task.available_tasks`.

Generating this list requires the gem to browse through the task
namespace(s) to find and load all tasks in the application (see
`Task#load_constants`).

Although in most situations this should not be a problem, the Shopify
monolith defines a relatively large amount of maintenance tasks, which
take a considerable amount of time to load.

Loading all tasks is only absolutely necessary when the application
wants to display a list of all tasks. In any other scenario, for example
testing a single task, it should not be necessary to load all tasks.

This commit makes that possible by making use of `Task.named` to confirm
that the task name is valid.
Because `available_tasks` relies on `.descendants`, and `Task.named`
uses ` < Task` to test inheritance, I don't think this will have any
unintended effect on the gem's behaviour.
Copy link
Member

@etiennebarrie etiennebarrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think that's what we really meant by checking for inclusion. We just wanted the task name to correspond to an existing task.

davidstosik added a commit that referenced this pull request Aug 18, 2023
In ordered to be displayed in the UI, tasks have to be loaded by the
time `MaintenanceTasks::Task.descendants` is called.
To achieve that, the `Task.load_constants` method was previously
implemented, and called from `Task.available_tasks`.

A few inconvenients of that method are:

- It could easily be abused and used in lower levels of the gem, where
  ideally it should ne be necessary to load all tasks to achieve the
  gem's goal. See for example my previous PR
  [Avoid loading all tasks to validate a
`Run#task_name`](#866).

- It can introduce unnecessary overhead. For example, displaying the
  CLI's basic help message with `maintenance_tasks help` will
  necessitate all tasks to be loaded (even though the help message does
  not display anything related to the available tasks), and this loading
  time will delay the appearance of that help message.
  Similarly, a single test case that want to test a single task's
  behaviour, while never displaying the whole list of available tasks,
  would be negatively impacted by a misuse of `Task.available_tasks`.

- It does not allow an application to define an alternative way to load
  all constants. As a matter of fact, the Shopify Core monolith
  monkey-patched that method to allow multiple "task modules" instead of
  a single one. With a configurable `task_loader`, monkey-patching would
  not be necessary. (Which is good. 🙈)

This PR introduces `MaintenanceTasks.task_loader`, a configuration that
allows applications to change the logic applied to ensure all task are
loaded before being displayed. As a bonus:

- it changes how the CLI generates the long description containing all
  task names, so that tasks get loaded at the last minute, only when
  necessary
- it gets rid of `Task.available_tasks` (its only use was for UI
  purposes, so I changed it to a direct call to `Task.descendants`)
- it introduces some tests of the loading behaviour
davidstosik added a commit that referenced this pull request Aug 18, 2023
In ordered to be displayed in the UI, tasks have to be loaded by the
time `MaintenanceTasks::Task.descendants` is called.
To achieve that, the `Task.load_constants` method was previously
implemented, and called from `Task.available_tasks`.

A few inconvenients of that method are:

- It could easily be abused and used in lower levels of the gem, where
  ideally it should ne be necessary to load all tasks to achieve the
  gem's goal. See for example my previous PR
  [Avoid loading all tasks to validate a
`Run#task_name`](#866).

- It can introduce unnecessary overhead. For example, displaying the
  CLI's basic help message with `maintenance_tasks help` will
  necessitate all tasks to be loaded (even though the help message does
  not display anything related to the available tasks), and this loading
  time will delay the appearance of that help message.
  Similarly, a single test case that want to test a single task's
  behaviour, while never displaying the whole list of available tasks,
  would be negatively impacted by a misuse of `Task.available_tasks`.

- It does not allow an application to define an alternative way to load
  all constants. As a matter of fact, the Shopify Core monolith
  monkey-patched that method to allow multiple "task modules" instead of
  a single one. With a configurable `task_loader`, monkey-patching would
  not be necessary. (Which is good. 🙈)

This PR introduces `MaintenanceTasks.task_loader`, a configuration that
allows applications to change the logic applied to ensure all task are
loaded before being displayed. As a bonus:

- it changes how the CLI generates the long description containing all
  task names, so that tasks get loaded at the last minute, only when
  necessary
- it gets rid of `Task.available_tasks` (its only use was for UI
  purposes, so I changed it to a direct call to `Task.descendants`)
- it introduces some tests of the loading behaviour
Copy link
Contributor

@adrianna-chang-shopify adrianna-chang-shopify left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

@adrianna-chang-shopify adrianna-chang-shopify merged commit 980f6dd into main Aug 23, 2023
34 checks passed
@adrianna-chang-shopify adrianna-chang-shopify deleted the sto/task-name-validate branch August 23, 2023 14:13
@shopify-shipit shopify-shipit bot temporarily deployed to rubygems August 23, 2023 14:48 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants